//--------------------------------------------------------------------------------------
// File: q3test.cpp
//
// Empty starting point for new Direct3D 9 and/or Direct3D 10 applications
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "DXUT.h"

#include "Quake3Model.h"
#include "D3D10Quake3Model.h"
#include "RenderState.h"
#include "FullScreenQuad.h"
#include "SoftwareRenderer.h"
#include "SoftwareQuake3Model.h"

using namespace er;

static D3D10Quake3Model *g_d3dModel = 0;
static RenderState g_renderState;
static ID3D10Texture2D *g_swOutputTex = 0;
static FullScreenQuad *g_quad = 0;
static SoftwareRenderer *g_swRenderer = 0;
static SoftwareQuake3Model *g_swModel = 0;

static float g_translation = 0.0f;
static float g_rotation = 0.0f;

//--------------------------------------------------------------------------------------
// Reject any D3D10 devices that aren't acceptable by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D10DeviceAcceptable( UINT Adapter, UINT Output, D3D10_DRIVER_TYPE DeviceType,
                                       DXGI_FORMAT BackBufferFormat, bool bWindowed, void* pUserContext )
{
    return true;
}


//--------------------------------------------------------------------------------------
// Called right before creating a D3D9 or D3D10 device, allowing the app to modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext )
{
    return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D10 resources that aren't dependant on the back buffer
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D10CreateDevice( ID3D10Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc,
                                      void* pUserContext )
{	
	std::string path = std::getenv("Q3DEMO");
	//path += "/models/players/sarge/upper.md3";
	path += "/models/weapons2/railgun/railgun.md3";
	RECT windowRect = DXUTGetWindowClientRect();

	Quake3ModelReader *reader = Quake3ModelReader::Create(path.c_str());
	
	if (reader == 0)
		goto create_reader_failed;
	
	g_d3dModel = D3D10Quake3Model::Create(reader, pd3dDevice);
	
	if (g_d3dModel == 0)
		goto create_d3dmodel_failed;

	g_quad = FullScreenQuad::Create(pd3dDevice);
	
	if (g_quad == 0)
		goto create_quad_failed;

	g_swRenderer = SoftwareRenderer::Create(
		(windowRect.right - windowRect.left) / 2,
		windowRect.bottom - windowRect.top);
		
	if (g_swRenderer == 0)
		goto create_swrenderer_failed;

	g_swModel = SoftwareQuake3Model::Create(reader, g_swRenderer);
	
	if (g_swModel == 0)
		goto create_swmodel_failed;

	Quake3ModelReader::Destroy(reader);
	
	return S_OK;
	
create_swmodel_failed:
	SoftwareRenderer::Destroy(g_swRenderer);
	g_swRenderer = 0;
	
create_swrenderer_failed:
	FullScreenQuad::Destroy(g_quad);
	g_quad = 0;
	
create_quad_failed:
	D3D10Quake3Model::Destroy(g_d3dModel);
	g_d3dModel = 0;
	
create_d3dmodel_failed:
	Quake3ModelReader::Destroy(reader);
		
create_reader_failed:
	return E_FAIL;
}


//--------------------------------------------------------------------------------------
// Create any D3D10 resources that depend on the back buffer
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D10ResizedSwapChain( ID3D10Device* pd3dDevice, IDXGISwapChain* pSwapChain,
                                          const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext )
{
	SAFE_RELEASE(g_swOutputTex);
	
	D3D10_TEXTURE2D_DESC td;
	td.Width = pBackBufferSurfaceDesc->Width / 2;
	td.Height = pBackBufferSurfaceDesc->Height;
	td.MipLevels = 1;
	td.ArraySize = 1;
	td.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	td.SampleDesc.Count = 1;
	td.SampleDesc.Quality = 0;
	td.Usage = D3D10_USAGE_DYNAMIC;
	td.BindFlags = D3D10_BIND_SHADER_RESOURCE;
	td.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
	td.MiscFlags = 0;
	
	pd3dDevice->CreateTexture2D(&td, NULL, &g_swOutputTex);	
	
	/*
	D3DX10CreateTextureFromFile(pd3dDevice, L"C:\\Users\\byrdjb\\Pictures\\backgrounds\\mac\\Small Ripples graphite.png",
		NULL, NULL, reinterpret_cast<ID3D10Resource **>(&g_swOutputTex), NULL);
	*/
	g_quad->SetTexture(g_swOutputTex);
	g_swRenderer->Resize(td.Width, td.Height);
	
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext )
{
}


//--------------------------------------------------------------------------------------
// Render the scene using the D3D10 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D10FrameRender( ID3D10Device* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
{
	RECT windowRect = DXUTGetWindowClientRect();	
	
	D3D10_VIEWPORT viewport;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;
	viewport.Width = (windowRect.right - windowRect.left) / 2;
	viewport.Height = windowRect.bottom - windowRect.top;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	float aspect = static_cast<float>(viewport.Width) / static_cast<float>(viewport.Height);
	
	D3DXMATRIX rotate, translate, view, proj;
	D3DXVECTOR3 eye(0.0f, 35.0f, 0.0f);
	//D3DXVECTOR3 eye(0.0f, 35.0f, 0.0f);
	D3DXVECTOR3 at(0.0f, 0.0f, 0.0f);
	D3DXVECTOR3 up(0.0f, 0.0f, 1.0f);

	//::D3DXMatrixIdentity(&world);
	//::D3DXMatrixRotationZ(&world, static_cast<float>(DXUTGetTime()) * 0.5f);
	::D3DXMatrixRotationZ(&rotate, g_rotation);
	::D3DXMatrixTranslation(&translate, 0.0f, 0.0f, g_translation);
	::D3DXMatrixLookAtLH(&view, &eye, &at, &up);
	::D3DXMatrixPerspectiveFovLH(&proj, 3.1415926f / 3.0f, aspect, 1.0f, 1000.0f);
	
	g_renderState.SetWorldMatrix(rotate * translate);
	g_renderState.SetViewMatrix(view);
	g_renderState.SetProjectionMatrix(proj);

	// Render in hardware on the left.
	pd3dDevice->RSSetViewports(1, &viewport);

	float clearColor[4] = { 0.0f, 0.0f, 0.25f, 0.0f };
	pd3dDevice->ClearRenderTargetView(DXUTGetD3D10RenderTargetView(), clearColor);
	pd3dDevice->ClearDepthStencilView(DXUTGetD3D10DepthStencilView(), D3D10_CLEAR_DEPTH, 1.0f, 0);

	g_d3dModel->Draw(g_renderState);
	
	// Render in software on the right.
	g_swRenderer->ClearColorBuffer(Color(0, 0, 64));
	g_swRenderer->ClearDepthBuffer(1.0f);
	
	/*
	float p = 10.0f;
	
	static D3DXVECTOR3 verts[] = 
	{
		D3DXVECTOR3(  -p, 0.0f, 0.0f),
		D3DXVECTOR3(0.0f, 0.0f,    p),
		D3DXVECTOR3(   p, 0.0f, 0.0f),
		D3DXVECTOR3(  -p, 0.0f, 0.0f),
		D3DXVECTOR3(0.0f, 0.0f,   -p),
		D3DXVECTOR3(   p, 0.0f, 0.0f),
	};
	
	g_swRenderer->SetPositionPointer(3, sizeof(D3DXVECTOR3), &verts[0].x);
	g_swRenderer->Draw(PRIMITIVE_TRIANGLES, sizeof(verts) / sizeof(verts[0]), 0, g_renderState);
	*/

	
	//g_swRenderer->SetColorMask(Color(0, 0, 0, 0));
	LARGE_INTEGER start, stop, freq;
	::QueryPerformanceCounter(&start);
	g_swModel->Draw(g_renderState);
	::QueryPerformanceCounter(&stop);
	::QueryPerformanceFrequency(&freq);
	
	double time = (stop.QuadPart - start.QuadPart) / (freq.QuadPart * 1.0e-3);
	
	char buffer[80];
	sprintf(buffer, "Q3Test: %.2f ms", time);
	::SetWindowTextA(::DXUTGetHWND(), buffer);
	
	//g_swRenderer->SetColorMask(Color(255, 255, 255, 255));
	//g_swModel->Draw(g_renderState);
		
	
	viewport.TopLeftX += viewport.Width;
	UInt32 width, height;
	g_swRenderer->GetSize(&width, &height);	

	D3D10_MAPPED_TEXTURE2D mapped;
	g_swOutputTex->Map(D3D10CalcSubresource(0, 0, 1), D3D10_MAP_WRITE_DISCARD, 0, &mapped);
	
	const Color *src = g_swRenderer->GetColorBufferPointer();
	UInt8 *dst = static_cast<UInt8 *>(mapped.pData);
	
	for (UInt32 i = 0; i < height; ++i)
	{
		std::memcpy(dst, src, width * sizeof(Color));
		src += width;
		dst += mapped.RowPitch;
	}
	
	g_swOutputTex->Unmap(D3D10CalcSubresource(0, 0, 1));
	
	
	pd3dDevice->RSSetViewports(1, &viewport);	
	g_quad->Draw();
}


//--------------------------------------------------------------------------------------
// Release D3D10 resources created in OnD3D10ResizedSwapChain 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D10ReleasingSwapChain( void* pUserContext )
{
	SAFE_RELEASE(g_swOutputTex);
}


//--------------------------------------------------------------------------------------
// Release D3D10 resources created in OnD3D10CreateDevice 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D10DestroyDevice( void* pUserContext )
{
	if (g_d3dModel != 0)
	{
		D3D10Quake3Model::Destroy(g_d3dModel);
		g_d3dModel = 0;
	}
	
	if (g_quad != 0)
	{
		FullScreenQuad::Destroy(g_quad);
		g_quad = 0;
	}
}


//--------------------------------------------------------------------------------------
// Handle messages to the application
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                          bool* pbNoFurtherProcessing, void* pUserContext )
{
    return 0;
}


//--------------------------------------------------------------------------------------
// Handle key presses
//--------------------------------------------------------------------------------------
void CALLBACK OnKeyboard( UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext )
{
	if (bKeyDown)
	{
		if (nChar == VK_LEFT)
			g_rotation += 0.05f;
		else if (nChar == VK_RIGHT)
			g_rotation -= 0.05f;
		else if (nChar == VK_DOWN)
			g_translation -= 0.00123f;
		else if (nChar == VK_UP)
			g_translation += 0.00123f;
	}
}


//--------------------------------------------------------------------------------------
// Handle mouse button presses
//--------------------------------------------------------------------------------------
void CALLBACK OnMouse( bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown,
                       bool bSideButton1Down, bool bSideButton2Down, int nMouseWheelDelta,
                       int xPos, int yPos, void* pUserContext )
{
}


//--------------------------------------------------------------------------------------
// Call if device was removed.  Return true to find a new device, false to quit
//--------------------------------------------------------------------------------------
bool CALLBACK OnDeviceRemoved( void* pUserContext )
{
    return true;
}


//--------------------------------------------------------------------------------------
// Initialize everything and go into a render loop
//--------------------------------------------------------------------------------------
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
    // Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

    // DXUT will create and use the best device (either D3D9 or D3D10) 
    // that is available on the system depending on which D3D callbacks are set below

    // Set general DXUT callbacks
    DXUTSetCallbackFrameMove( OnFrameMove );
    DXUTSetCallbackKeyboard( OnKeyboard );
    DXUTSetCallbackMouse( OnMouse );
    DXUTSetCallbackMsgProc( MsgProc );
    DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );
    DXUTSetCallbackDeviceRemoved( OnDeviceRemoved );

    // Set the D3D10 DXUT callbacks. Remove these sets if the app doesn't need to support D3D10
    DXUTSetCallbackD3D10DeviceAcceptable( IsD3D10DeviceAcceptable );
    DXUTSetCallbackD3D10DeviceCreated( OnD3D10CreateDevice );
    DXUTSetCallbackD3D10SwapChainResized( OnD3D10ResizedSwapChain );
    DXUTSetCallbackD3D10FrameRender( OnD3D10FrameRender );
    DXUTSetCallbackD3D10SwapChainReleasing( OnD3D10ReleasingSwapChain );
    DXUTSetCallbackD3D10DeviceDestroyed( OnD3D10DestroyDevice );

    // Perform any application-level initialization here

    DXUTInit( true, true, NULL ); // Parse the command line, show msgboxes on error, no extra command line params
    DXUTSetIsInGammaCorrectMode(false);
    DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
    DXUTCreateWindow( L"q3test" );
    DXUTCreateDevice( true, 1200, 600 );
    DXUTMainLoop(); // Enter into the DXUT render loop

    // Perform any application-level cleanup here

    return DXUTGetExitCode();
}
